Skip to content

ai-assistant: appbar: Register events after render - #1247

Merged
skoeva merged 1 commit into
headlamp-k8s:mainfrom
illume:ai-assistant-fix-event-registration
Oct 30, 2026
Merged

skoeva merged 1 commit into
headlamp-k8s:mainfrom
illume:ai-assistant-fix-event-registration

Conversation

@illume

@illume illume commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor
  • Impact: The AI Assistant can trigger React’s update-during-render warning. Rerenders accumulate navigation callbacks, causing duplicate assistant-context updates and leaving handlers active after unmount.
  • Fix: Register after render, keep one callback using current state, and detach unmounted handlers. Tests cover rerenders, StrictMode, and cleanup. As well there are e2e tests to prove this is fixed, and prevent regressions in the future.

Was seeing this

Warning: Cannot update a component (`Plugins`) while rendering a different component (`qG`). To locate the bad setState() call inside `qG`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render Error Component Stack
    at qG (../src/components/appbar/HeadlampEventHandler.tsx:38:24)
    at ErrorBoundary (ErrorBoundary.tsx:55:1)
    at AppBarActions (TopBar.tsx:248:3)
    at div (<anonymous>)
    at chunk-F3CYMQTH.js:2916:38
    at Toolbar2 (chunk-XAIC7LRB.js:126:17)
    at nav (<anonymous>)
    at chunk-F3CYMQTH.js:2916:38
    at Paper2 (chunk-FWZRMJK5.js:148:17)
    at chunk-F3CYMQTH.js:2916:38
    at AppBar2 (chunk-76NB2BJB.js:186:17)
    at TopBar.tsx:277:5
    at TopBar (TopBar.tsx:102:32)
    at div (<anonymous>)
    at chunk-F3CYMQTH.js:2916:38
    at Box3 (chunk-F2ZA2UMM.js:87:19)
    at div (<anonymous>)
    at chunk-F3CYMQTH.js:2916:38
    at Box3 (chunk-F2ZA2UMM.js:87:19)
    at Layout (Layout.tsx:201:32)
    at MonacoEditorLoaderInitializer (MonacoEditorLoaderInitializer.tsx:29:49)
    at PreviousRouteProvider (RouteSwitcher.tsx:244:41)
    at Router2 (chunk-4MSBMKCL.js:1598:30)
    at BrowserRouter2 (react-router-dom.js:71:35)
    at Router (AppContainer.tsx:160:19)
    at SnackbarProvider2 (notistack.js:1251:24)
    at AppContainer (AppContainer.tsx:168:59)
    at DefaultPropsProvider (chunk-FO5STQHE.js:31:3)
    at RtlProvider (chunk-CCQBJLQB.js:194:5)
    at ThemeProvider (chunk-CCQBJLQB.js:141:5)
    at ThemeProvider2 (chunk-CCQBJLQB.js:356:5)
    at ThemeProvider (chunk-QUXTRDVZ.js:343:12)
    at StyledEngineProvider (chunk-F3CYMQTH.js:3602:5)
    at ThemeProviderNexti18n (ThemeProviderNexti18n.tsx:126:40)
    at I18nextProvider (I18nextProvider.js:4:3)
    at AppWithRedux (App.tsx:39:3)
    at QueryClientProvider (chunk-D4U4PBCO.js:3312:3)
    at Provider (react-redux.js:1013:11)
    at ErrorBoundary (ErrorBoundary.tsx:55:1)
    at App (<anonymous>)

Testing

  • CI=true npm run check

  • npm run e2e -- event-registration.spec.ts --repeat-each=3: 6/6 passed across two browser scenarios. Restoring render-time registration makes the callback-count regression fail (8 callbacks instead of 1). The tests use real Headlamp/KWOK navigation and send no model requests.

  • All 10 event-handler unit tests, root/e2e TypeScript, and targeted ESLint/Prettier checks pass.

Manual testing

  1. Load this PR’s plugin in a development build of Headlamp, with a configured AI provider and a cluster containing two pods and a Deployment. Start with an empty chat; do not send any prompts.
  2. Open browser DevTools → Console, enable Preserve log, clear it, and reload Headlamp. Open the AI Assistant and navigate to a pod’s details. Click How can I debug this pod?: the inserted draft should name that pod.
  3. Clear the draft, navigate to the other pod, and click the same suggestion. The draft must now name the second pod, not the first. No AI request or resource change is needed.
  4. Visit a Deployment’s details, then return to a pod; close/reopen the assistant and repeat navigation. Suggestions should follow the current resource kind. The console must not show Cannot update a component (Plugins) while rendering a different component.

Exact callback registration counts and unmount cleanup are covered by the unit tests, not this UI smoke test.

Assisted by copilot.

@illume
illume force-pushed the ai-assistant-fix-event-registration branch from 9e9e8e9 to 399fe74 Compare August 25, 2026 16:10
@illume
illume requested a balanced review from Copilot August 25, 2026 16:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR moves Headlamp event callback registration out of render and into a post-commit effect to avoid React’s “setState during render” warning, while keeping the callback reading the latest plugin state via a ref.

Changes:

  • Register registerHeadlampEventCallback inside useEffect instead of during render.
  • Use a useRef to keep the event handler wired to the latest useGlobalState() value without re-registering.
  • Update tests to render the component and assert single registration across rerenders.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
ai-assistant/src/components/appbar/HeadlampEventHandler.tsx Moves callback registration into useEffect and uses a ref to avoid stale state closures.
ai-assistant/src/components/appbar/HeadlampEventHandler.test.tsx Renders the component in tests and adds coverage for single registration on rerender.
Suppressed comments (1)

ai-assistant/src/components/appbar/HeadlampEventHandler.test.tsx:40

  • After moving registration into useEffect, the callback is registered post-commit rather than during render. This helper reads registerHeadlampEventCallback.mock.calls immediately after render, which can become timing-sensitive depending on React/test environment. To avoid flakes, wait until the mock has been called (e.g., waitFor an expected call count) before accessing .mock.calls.at(-1).
async function getEventCallback() {
  const { default: HeadlampEventHandler } = await import('./HeadlampEventHandler');
  render(<HeadlampEventHandler />);
  return registerHeadlampEventCallback.mock.calls.at(-1)![0] as (event: {
    type: string;
    data?: unknown;
  }) => null;
}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@illume
illume force-pushed the ai-assistant-fix-event-registration branch from 399fe74 to 0ebf5b2 Compare August 25, 2026 16:50
@illume
illume requested a balanced review from Copilot August 25, 2026 16:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@illume illume added ai-assistant bug Something isn't working labels Oct 11, 2026
@illume
illume force-pushed the ai-assistant-fix-event-registration branch from 3f846b2 to 952c17f Compare October 29, 2026 12:13
@illume

illume commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The rerender test uses unchanged mocked state and setter, so it cannot detect stale callback state capture or verify that the original callback uses the updated setter without re-registration.

1 open issue

Review effort: Balanced

@illume
illume requested a balanced review from Copilot October 29, 2026 13:18
@illume
illume force-pushed the ai-assistant-fix-event-registration branch from 952c17f to 96e9008 Compare October 29, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The rerender test keeps the same mocked plugin state, so it cannot verify that the existing callback uses a replacement setter without registering again.

1 open issue
1 resolved since last review

Review effort: Balanced

@illume
illume requested a balanced review from Copilot October 29, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are recorded for the event-registration lifecycle changes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Register the Headlamp callback after commit to avoid dispatching to the store
during React render.

Signed-off-by: René Dudfield <renedudfield@microsoft.com>
@illume
illume force-pushed the ai-assistant-fix-event-registration branch from 96e9008 to 4687c0a Compare October 29, 2026 14:34
@illume

illume commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Added two permanent Chromium/KWOK regressions in 4687c0a, folded into the existing atomic commit:

  • The real host event registry stays at one callback through resource navigation and assistant rerenders.
  • Pod/deployment drafts use the currently viewed resource after navigation and panel reopening, without sending model requests.

Verified failing-before behavior by temporarily restoring render-time registration: the callback-count test observed 8 callbacks instead of 1. Restored the production fix byte-for-byte; both scenarios then passed three consecutive runs (6/6). The 10 unit tests, root/e2e type checks, and targeted lint/format checks also pass. Production React omits development warnings, so the first scenario uses a read-only test probe of the real host callback registry rather than relying on console silence.

The summary now includes the requested e2e sentence, and Testing includes the before/after evidence.

@BryanOnKeys BryanOnKeys left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4687c0adf161ca74ffb2154da3cf1b6a6e2048a3 with AI assistance.

I checked the installed SDK implementation: registerHeadlampEventCallback dispatches the registration and returns void, so the single host dispatcher with mount-scoped subscriber cleanup fits the API available here.

Independent local validation:

  • npm ci --no-audit --no-fund and npm test -- src/components/appbar/HeadlampEventHandler.test.tsx: all 10 tests passed.
  • A temporary additional test unmounted the first component, mounted a fresh instance, and dispatched through the original host callback. All 11 tests passed: registration stayed at one, only the replacement setter received the event, and delivery stopped after the second unmount.
  • Temporarily removing pluginStateRef.current = _pluginState made the latest-setter regression fail (9 passed, 1 failed). After restoring the exact published files, all 10 tests passed again.
  • npm run tsc passed.

I found no additional issue in the registration/cleanup behavior reviewed. The temporary experiment was removed. I did not run the Docker/KWOK end-to-end scenarios; these results cover the unit-level lifecycle and type check only.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are blocking approval of the event-registration lifecycle changes.

No open issues 🎉

Review effort: Balanced

@skoeva skoeva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thanks

@skoeva
skoeva merged commit 851fbb8 into headlamp-k8s:main Oct 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assistant bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants